Calling the Mathematica Kernel as a computational engine from a C program via MathLink

Steve Wilson
Wolfram Research, Inc.

By making use of the MathLink Application Programming Interface, C programmers can extend a program's functionality by harnessing the full power of the Mathematica Kernel from within their own program.  These exercises will familiarize the programmer with the techniques necessary to establish a connection with the Mathematica Kernel, request arbitrary evaluations, and read the results.

Component Description

For the purposes of this exercise you will use the following elements from the MathLink Developer Kit:  mathlink.h, libML32i3.a, and mcc.  The header file mathlink.h contains all the data type and function definitions for the MathLink C Application Programming Interface.  Here are a few of the functions from mathlink.h:

MLDECL( MLEnvironment, MLInitialize,   ( MLParametersPointer p));
MLDECL( MLINK,         MLOpenArgcArgv,  ( MLEnvironment ep, char** argv, char** argv_end, long* errp));
MLDECL( mlapi_result,   MLPutInteger,       ( MLINK mlp, int i));
MLDECL( mlapi_result,   MLPutLongInteger,   ( MLINK mlp, long l));
MLDECL( mlapi_result,   MLGetInteger,       ( MLINK mlp, int* ip));
MLDECL( mlapi_result,   MLGetLongInteger,   ( MLINK mlp, long* lp));
.
.
.
MLDECL( void,          MLClose,           ( MLINK mlp));
MLDECL( void,          MLDeinitialize, ( MLEnvironment env));

Don't be bothered by the MLDECL() C preprocessor macro.  You can think of the macro as having the following definition:

#define MLDECL(returntype, functionname, arguments) \
	returntype functionname(arguments)

The programmer creates a MathLink program by using the MathLink API in a C/C++ program and linking that program with the MathLink library; the library of C functions that implement the MathLink protocol.

In order to compile your solutions to the exercises below, you will need to set yourself up to make use of mcc the compiler driver included in the Unix and Mac OS X MathLink Developer Kits.  To use mcc you need to open a terminal window on your machine.  (If you are using Mac OS X, you need to launch /Applications/Utilities/Terminal.app, on Linux you can just create a new xterm, and on Windows you will need to open a window that gives you access to an OS X or Linux machine.  You can complete all the exercises on a Windows machine, but that requires a compiler such as Microsoft's Visual Studio .NET.  The instructions below typically refer to the Unix/OS X environments for the sake of brevity.)  Determine the type of interactive shell you are using by running the shell command:

prompt> echo $SHELL

For /bin/tcsh and /bin/bash use the alias command to set up an mcc alias.  For OS X and tcsh:

prompt> alias mcc '/Applications/Wolfram/Mathematica\ 6.0.app/SystemFiles/Links/MathLink/DeveloperKit/MacOSX/CompilerAdditions/mcc'

For /bin/bash or /bin/sh and OS X use:

prompt> alias mcc="/Applications/Mathematica\ 6.0.app/SystemFiles/Links/MathLink/DeveloperKit/MacOSX/CompilerAdditions/mcc"

On Unix machines you can find the MathLink Developer Kit in /usr/local/Wolfram/Mathematica/6.0/SystemFiles/Links/MathLink/DeveloperKit/$SystemID.

You can use mcc to compile your binaries as follows:

mcc -o exercise exercise.c

Preliminaries

The MathLink Environment

Every MathLink program has at least one MathLink environment.  You can think of the MathLink environment as a factory that creates link objects.  The MathLink library represents environment objects using the MLENV data type.  The following code calls the MathLink API function MLInitialize() to create the environment object.

MLENV env;

env = MLInitialize((MLParametersPointer)0);

For now, do not worry about the MLParametersPointer argument.  If MLInitialize() fails it returns NULL.  In addition to the MLInitialize() function which creates the MathLink environment object, the library also has a function MLDeinitialize() that destroys the MathLink environment object.

MLDeinitialize(env);

Every MathLink program must use MLInitialize() to create the environment before creating, connecting and using any links.  Similarly every MathLink program needs to call MLDeinitialize() when finished using MathLink to free allocated resources.

Creating the Link

Having created the MathLink environment we can now create, connect, use, and destroy link objects.  A link object represents a link to another program and most of the MathLink API functions operate directly on link objects.  The MathLink library represents link objects using the MLINK data type.

To create a link object, MathLink needs to know several details of the type of connection you want to make.  In general MathLink needs four items of information to open a link: The name of link, the mode of the link, the link protocol, and any link options.  You can create links in one of three different modes: Listen, Connect, or Launch.  The Listen mode tells MathLink to create a link object and listen on that object for another MathLink program to connect.  The Connect mode tells MathLink to create a link and initiate a connection to another listening MathLink program.  The Launch mode tells MathLink to start another program and make the connection in a parent/child type of relationship.  MathLink also needs to know the connection protocol to use to establish the link.  You can think of the protocol as the underlying transport mechanism.  The protocol could be TCPIP, indicating that MathLink should use the TCP/IP network protocol as the underlying transport mechanism, the interprocess communication mechanism(IPC).  The protocol could be Pipes, indicating that MathLink should use Unix pipes as the IPC mechanism.  The protocol could be SharedMemory to indicate that MathLink should use a shared memory space between processes as the IPC mechanism.  In most link creation scenarios MathLink supports a default link protocol for the link mode of the new link so that programmers do not have to include the link protocol when creating the link.  The name of the link is determined based on both the mode of the link and the protocol in use.  For example when creating a link in Connect mode using the TCPIP protocol, the link name will need to be of the form port@hostname, ie something like 34566@remotehost.remotedomain.com.  However, when using Launch mode with the Pipes protocol the link name needs to be the path to the binary for MathLink to launch and any arguments needed to start the binary.  For example a Launch command link name might be '/Applications/Mathematica\ 6.0.app/Contents/MacOS/MathKernel -mathlink"  which would start the Mathematica Kernel on a Mac OS X machine in MathLink mode.  MathLink also supports various link options that apply to the different modes and protocols but those are beyond the scope of this discussion.

To create a link object, we need to use one of the MLOpen() MathLink API functions.  For simplicity we will use the function MLOpenArgcArgv() which allows us to pass command line parameters to MathLink from the command line used to invoke our program.

Here is the prototype for MLOpenArgv() from mathlink.h:

MLDECL( MLINK,         MLOpenArgcArgv,      ( MLEnvironment ep, char** argv, char** argv_end, long* errp));

The following program illustrates the use of MLOpenArgcArgv() to open a link using the arguments to a program from the command line:

#include "mathlink.h"

int main(int argc, char **argv)
{
	MLENV env;
	MLINK link;
	long err;

	env = MLInitialize((MLParametersPointer)0);
	
	link = MLOpenArgv(env, argv, argv + argc, &err);
	
	MLClose(link);
	
	MLDeinitialize(env);
	
	return 0;
}

From the command line we might run this program as follows:

myprogram -linkmode Launch -linkname "math -mathlink"

Instead of using arguments from the command line we could hard code the arguments in the program source:

int argc = 4;
char *argv[5] = {"-linkname",
                 "math -mathlink",
                 "-linkmode",
                 "Launch",
                 NULL};

The previous example demonstrates a launch command on Unix.  On Windows the link name line might instead read:

C:\path\to\myprogram -linkmode Launch -linkname "C:\\Program\ Files\\Wolfram\ Research\\Mathematica\\6.0\\math -mathlink"

If MLOpenArgcArgv() fails it returns NULL.

Connecting the Link

Once you have successfully created the link, you must actually tell the library to fully establish the connection with the program on the other side of the link.  This can be accomplished in one of two ways.  First the programmer can just start using MathLink API functions and let the library make the connection for you when you call the first MathLink function.  This method however could cause the first MathLink call to block indefinitely until the link has fully connected.  As an alternative (and I recommend this method) the programmer can call MLConnect() to fully establish the link with the other program.  In the following simple program we call MLConnect() immediately after the MLOpenArgcArgv() call to make sure that the library has fully established the connection.

#include "mathlink.h"

int main(int argc, char **argv)
{
	MLENV env;
	MLINK link;
	long err;

	env = MLInitialize((MLParametersPointer)0);
	
	link = MLOpenArgcArgv(env, argv, argv + argc, &err);
	
	MLConnect(link);
	
	MLClose(link);
	
	MLDeinitialize(env);
	
	return 0;
}

MLConnect() also blocks indefinitely until the library finishes establishing the connection.  In order to determine when best to call MLConnect() so that your program does not block indefinitely when it could do other useful work, the programmer should call MLReady() to check to see it the link connection will succeed.  MLReady() functions in two modes.  First, prior to link connection, MLReady()'s return value indicates whether or not a connection attempt will succeed, ie whether or not the MLConnect() attempt will block or not.  Second, after the link connection, the MLReady() return value indicates whether or not a link has incoming data that the program can read through one of the MLGet*() functions.

In terms of establishing creating a link and establishing a connection we now have the complete tools necessary to put together simple MathLink programs. The following simple program demonstrates an example that sends a long integer across a link and waits for a result.

#include "stdio.h"
#include "mathlink.h"

int main(int argc, char **argv)
{
        MLENV env;
        MLINK link;
        long err;

        int val;
        long a;

        mlapi_result result;

        env = MLInitialize((MLParametersPointer)0);
		if(env == (MLENV)0) goto E0;

        if(argc > 1){
                link = MLOpenArgcArgv(env, argv + 1, argv + argc, &err);
                if(err != MLEOK) goto E1;
        }
        else{
                printf("Not enough arguments to open link\n");
                goto E1;
        }

        MLConnect(link);

        a = 100000;

        result = MLPutLongInteger(link, a);
        if(result != MLSUCCESS) goto E2;

        MLFlush(link);

        while(! MLReady(link)) sleep(1);
        result = MLGetInteger(link, &val);
        if(result != MLSUCCESS) goto E2;

        printf("val: %d\n",val);

		MLClose(link);
		MLDeinitialize(env);
		return 0;

E2:     MLClose(link);
E1:     MLDeinitialize(env);
E0:     return 1;
}

Communicating with the Kernel

Expressions

In order to communicate with the Mathematica Kernel, the MathLink programmer needs to understand and use the Kernel's notion of Mathematical Expressions.  For a full treatment of expressions I refer you to the Mathematica book, section 2.1.  MathLink supports sending two types of expressions.  The first type of expressions are expressions that cannot be broken down into smaller components.  Strings, and numbers make up this class of expressions and the MathLink library contains appropriate MLPut*() and MLGet*() functions for each of the basic C data type versions of strings and numbers.  MathLink treats Mathematica symbols as strings.  You can use MLPutSymbol() and MLGetSymbol() to transfer non composite expressions (ie x, but not (x^2)), but the arguments are always strings.  The second type of expressions are the composite expressions.  Composite expressions have a head and some number of sub expressions.  MathLink transfers expressions in the same manner as the FullForm[] description of the expressions.  Thus MathLink treats {1,2,3} as List[1,2,3] and 2+2 as Plus[2,2].  In order to transfer these composite expressions, the programmer must use the MLPutFunction() API function to send the head, and the number of arguments in the expression.  Thus to send the expression {1,2,3x3.0,g[x]} the MathLink function calls would look like this:

MLPutFunction(link, "List", 4);
  MLPutInteger(link, 1);
  MLPutInteger(link, 2);
  MLPutFunction(link, "Times", 2);
    MLPutInteger(link, 3);
    MLPutDouble(link, 3.0);
  MLPutFunction(link, "g", 1);
    MLPutSymbol(link, "x");
MLEndPacket(link);

We will discuss the use of MLEndPacket() in the following sections.  If at any time you need to see the FullForm[]  version of an expression you can use Hold[expression]//FullForm in a Mathematica session.

Packets

All communication with the Mathematica Kernel happens through the use of packets.  These Kernel packets do not refer to other types of packets such as the packets used with the Internet Protocol (IP), rather they refer to idea that all input and output from the kernel come wrapped in an expression packet.  For example sending input to the Kernel requires wrapping the expression in EvaluatePacket[], EnterTextPacket[], or EnterExpressionPacket[].  Results returned from the Kernel will come wrapped in packets such as ReturnPacket[] or ReturnTextPacket[].  For the purposes of this exercise we will omit discussing EnterTextPacket[], EnterExpressionPacket[], ReturnTextPacket[].  For a more thorough discussion of when to use these different packet types see the MathLink tutorial notebook written by Todd Gayley, sections 2.3 and 2.4.

MathLink considers these Kernel packets just composite expressions just as any other Mathematica expression.  Thus to have the Kernel evaluate D[x^2+2x+3,x], you would call the following sequence of MathLink API functions:

MLPutFunction(link, "EvaluatePacket", 1);
  MLPutFunction(link, "D", 2);
    MLPutFunction(link, "Plus", 3);
      MLPutFunction(link, "Power", 2);
        MLPutSymbol(link, "x");
        MLPutInteger(link, 2);
      MLPutFunction(link, "Times", 2);
        MLPutInteger(link, 2);
        MLPutSymbol(link, "x");
      MLPutInteger(link, 3);
    MLPutSymbol(link, "x");
MLEndPacket(link);

The Mathematica Kernel will send the response to an EvaluatePacket[] (ie the result), wrapped in a ReturnPacket[].

MLNewPacket, MLNextPacket, and MLEndPacket

When sending a packet to the Kernel, the programmer should always call MLEndPacket() at the completion of sending the packet expression.  MLEndPacket() inserts an end-of-packet token into the data stream.  On the receiving side of the link MathLink uses the end-of-packet token to delimit packet boundaries.  Thus, when a mistake in reading the current packet occurs or the program determines it does not need to read the current packet, it can skip ahead in the stream to the next complete packet.

When sending input to the Kernel be sure and follow the MLEndPacket() with MLFlush().  MathLink buffers the data that it sends across the link.  In order to be sure the packet gets sent you need to flush the link.  Otherwise you may send a packet to the Kernel and then call MLNextPacket() to get the Kernel's response while MathLink has not yet sent your input packet.

To determine the current incoming packet type, the programmer can call the API function MLNextPacket().

MLDECL( mlapi_packet,  MLNextPacket, ( MLINK mlp));

The MathLink header file mathlink.h defines the packet types that MLNextPacket() can return.  Here are a few packets from that list:

In order to read the result from a Kernel evaluate you can employ the following piece of code:

while((packet = MLNextPacket(link)) && packet != RETURNPKT)
           MLNewPacket(link);

Once MLNextPacket() returns the packet type that you need to read, you can call the MLGet*() functions to read the contents of the packet off the link.

The MathLink API function MLNewPacket() tells the MathLink library to skip ahead in the expression stream to the next complete packet, effectively discarding the current packet in the stream.  In the code snippet above, the code uses MLNextPacket() to check the current packet type and calls MLNewPacket() to skip to the next packet if the current packet is not a ReturnPacket[].  *WARNING* Using this piece of code can cause problems with your program.  The code assumes that the Kernel will send a ReturnPacket[] across the link.  If the Kernel does not actually send a ReturnPacket[] (say for example the Kernel returns a ReturnTextPacket[] instead), then the program will hang waiting for input from the Kernel that will never arrive.  The programmer should also keep in mind that the Kernel will return a ReturnPacket[] for every evaluation it receives, even if the result is Null.

Reading A Packet

After getting the packet you can use MLGetNext() to return the token type of the incoming data.  The token value will tell you if the incoming data is an integer, real number, string, symbol, etc.

The following code from $TopDirectory/SystemFiles/Links/MathLink/DeveloperKit/$SystemID/MathLinkExamples/factor2.c demonstrates a way to read the contents of a packet from the link:

static void read_and_print_expression( MLINK lp)
{
        const char *s;
        int n;
        long i, len;
        double r;
        static int indent;

        switch( MLGetNext( lp)) {
        case MLTKSYM:
                MLGetSymbol( lp, &s);
                printf( "%s ", s);
                MLDisownSymbol( lp, s);
                break;
        case MLTKSTR:
                MLGetString( lp, &s);
                printf( "\"%s\" ", s);
                MLDisownString( lp, s);
                break;
        case MLTKINT:
                MLGetInteger( lp, &n);
                printf( "%d ", n);
                break;
        case MLTKREAL:
                MLGetReal( lp, &r);
                printf( "%g ", r);
                break;
        case MLTKFUNC:
                indent += 3;
                printf( "\n %*.*s", indent, indent, "");
                if( MLGetArgCount( lp, &len) == 0){
                        error( lp);
                }else{
                        read_and_print_expression( lp);
                        printf( "[");
                        for( i = 1; i <= len; ++i){
                                read_and_print_expression( lp);
                                if( i != len) printf( ", ");
                        }
                        printf( "]");
                }
                indent -= 3;
                break;
        case MLTKERROR:
        default:
                error( lp);
        }
}

The function MLGetNext() returns the token type of the next data available on the link.  These token types correspond to basic C data types.  For example MLTKSTR - C string, MLTKINT - C int, MLTKREAL - C double.  The MathLink header file mathlink.h has the list of possible MLTK tokens.  Here are a few tokens and the MathLink API functions to use to retrieve the values on the link.

When retrieving symbols and strings, the MathLink library allocates space for the string or symbol and then passes a pointer to that memory to the program through the MLGetString()/MLGetSymbol() function.  The programmer may read that memory and copy it, but should not alter it or try and free the memory.  When the programmer has finished using the memory they should call MLDisownString()/MLDisownSymbol() to indicate to the library that it can free the resources used by the string or symbol.

Sending and Receiving Simple Expressions

Sometimes, for simple evaluations, sending a complicated expression structure using all the MLPut() functions can complicate sending an evaluation unnecessarily.  In that case you can use EvaluatePacket[ToExpression["string"]] to send a string version of the expression for easy evaluation.  For example to evaluate Integrate[x^2,x] using this method, you can do the following:

MLPutFunction(link, "EvaluatePacket", 1);
  MLPutFunction(link, "ToExpression", 1);
    MLPutString(link, "Integrate[x^2,x]");
MLEndPacket(link);

In order to have Mathematica return its result as a string rather than using full expressions you can do the following:

MLPutFunction(link, "EvaluatePacket", 1);
  MLPutFunction(link, "ToString", 1);
    /* Send the rest of the expression here */
MLEndPacket(link);

Combining these two techniques would give you the MathLink equivalent of EvaluatePacket[ToString[ToExpression["string"]]] or:

MLPutFunction(link, "EvaluatePacket", 1);
  MLPutFunction(link, "ToString", 1);
    MLPutFunction(link, "ToExpression", 1);
      MLPutString(link, "Integrate[x^2,x]");
MLEndPacket(link);

When Mathematica sends its result as a string you simply have to find the ReturnPacket[] and call MLGetString() to get the string result.

Exercises

Each of the exercises below includes a possible solution under the Answer section.  Please try to solve the exercise on your own before examining the contents of the Answer sections.

Exercise 1

For exercise one, use the information in the preliminaries section to write a simple program that creates a link to the Mathematica Kernel, asks the kernel to evaluate Integrate[1/Sin[x],x] and prints the results received from the Kernel.  Use EvaluatePacket[] and the MLPut*() functions to send the expression.  Create a function that reads the results and prints the output from the Kernel.

Answer

Here is a simple program that solves exercise 1:

/*
 *  exercise1.c
 *
 *
 *  Created by Steve Wilson on 10/5/06.
 *  Copyright 2006 Wolfram Reasearch Inc.. All rights reserved.
 *
 */

#include <stdio.h>
#include "mathlink.h"

void read_expressions(MLENV, MLINK);

int main(int argc, char **argv)
{
        MLENV env;
        MLINK link;
        long error;
        mlapi_packet pkt;
        const char *result;
        mlapi_result mlresult;

        env = MLInitialize((MLParametersPointer)0);
        if(env == (MLENV)0) goto E0;

        link = MLOpenArgcArgv(env, argv, argv + argc, &error);
        if(link == (MLINK)0) goto E1;

        MLConnect(link);

        MLPutFunction(link, "EvaluatePacket", 1);
                MLPutFunction(link, "Integrate", 2);
                        MLPutFunction(link, "Times", 2);
                                MLPutInteger(link, 1);
                                MLPutFunction(link, "Power", 2);
                                        MLPutFunction(link, "Sin", 1);
                                                MLPutSymbol(link, "x");
                                        MLPutInteger(link, -1);
                        MLPutSymbol(link, "x");
        MLEndPacket(link);

        MLFlush(link);

        while((pkt = MLNextPacket(link)) != RETURNPKT) MLNewPacket(link);

        read_expressions(env, link);

        printf("\n"); /* To make sure we reset the output line */

        MLClose(link);
        MLDeinitialize(env);

        return 0;

E2:     MLClose(link);
E1:     MLDeinitialize(env);
E0:     return 1;
}


void read_expressions(MLENV env, MLINK link)
{
        const char *str;
        int integer;
        double real;
        long i, length;

        switch(MLGetNext(link)){
                case MLTKSYM:
                        MLGetSymbol(link, &str);
                        printf("%s", str);
                        MLDisownSymbol(link,str);
                        break;
                case MLTKSTR:
                        MLGetString(link, &str);
                        printf("\"%s\"", str);
                        MLDisownString(link, str);
                        break;
                case MLTKINT:
                        MLGetInteger(link, &integer);
                        printf("%d", integer);
                        break;
                case MLTKREAL:
                        MLGetReal(link, &real);
                        printf("%g", real);
                        break;
                case MLTKFUNC:
                        MLGetArgCount(link, &length);
                        read_expressions(env, link);
                        printf("[");
                        for(i = 0; i < length; i++){
                                read_expressions(env, link);
                                if(i != (length - 1)) printf(", ");
                        }
                        printf("]");
                        break;
                case MLTKERROR:
                        printf("MathLink generated an error: %s", MLErrorString(env, MLError(link)));
                        MLClearError(link);
        }
}

Exercise 2

Modify Exercise 1 so that you simplify the input to the Kernel using ToExpression[] and ToString[].  Modify the way Exercise 1 reads the results so that you can simply read a string from the Kernel.

Answer

Here is a simple program that solves Exercise 2.

/*
 *  exercise2.c
 *
 *
 *  Created by Steve Wilson on 10/5/06.
 *  Copyright 2006 Wolfram Reasearch Inc.. All rights reserved.
 *
 */

#include <stdio.h>
#include "mathlink.h"

int main(int argc, char **argv)
{
        MLENV env;
        MLINK link;
        long error;
        mlapi_packet pkt;
        const char *result;
        mlapi_result mlresult;

        env = MLInitialize((MLParametersPointer)0);
        if(env == (MLENV)0) goto E0;

        link = MLOpenArgcArgv(env, argv, argv + argc, &error);
        if(link == (MLINK)0) goto E1;

        MLConnect(link);

        MLPutFunction(link, "EvaluatePacket", 1);
                MLPutFunction(link, "ToString", 1);
                        MLPutFunction(link, "FullForm", 1);
                                MLPutFunction(link, "ToExpression", 1);
                                        MLPutString(link, "Integrate[1/Sin[x],x]");
        MLEndPacket(link);

        MLFlush(link);

        while((pkt = MLNextPacket(link)) != RETURNPKT) MLNewPacket(link);

        mlresult = MLGetString(link, &result);
        if(mlresult != MLSUCCESS){
                printf("MathLink generated an error: %s\n", MLErrorString(env, MLError(link)));
                goto E2;
        }

        printf("%s\n", result);

        MLDisownString(link, result);

        MLClose(link);
        MLDeinitialize(env);

        return 0;

E2:     MLClose(link);
E1:     MLDeinitialize(env);
E0:     return 1;
}

Exercise 3

For this exercise you will need to write a program that creates a very small text front end to the Mathematica Kernel.  Before you begin, you should know about several other packets that the kernel may send across the link.  You can find the MathLink preprocessor name that defines the following packets in mathlink.h.

First, we need to handle the InputNamePacket[] and the OutputNamePacket[].  These two packets will contain strings for the In[]:= and Out[]= prompts.  You can read the contents in the same way you would read a ReturnPacket[] and use the results for In and Out prompts for your text 'FrontEnd.'  In addition, instead of sending expressions wrapped in EvaluatePacket[], you will want to wrap the expressions with EnterExpressionPacket[].  EnterExpressionPacket[] requests that the Kernel pass the expression through the main loop evaluation process.  This means that the Kernel will send InputNamePacket[] and OutputNamePacket[] packets to provide the standard interface expected from the Kernel.  As a result, instead of looking for ReturnPacket[] your code should check for ReturnExpressionPacket[] or RETURNEXPRPKT as defined in mathlink.h.

Second, we need to handle the Kernel's MessagePacket[].  The Kernel sends the MessagePacket[] whenever it generates a message as part of an evaluation.  For completeness you will want to output the Kernel messages so the user of your front end program knows when kernel issues warnings and errors.  The MessagePacket[] contains two entries.  The first expression in the packet contains the name of the message.  The second contains the string indicating the error.  So for the following error ToExpression::sntxi, "ToExpression" is the first string off the link followed by the string "snxti".  The Kernel follows a MessagePacket[] with a TextPacket[] that contains a string detailing the full message.

Finally, while not pertaining to Kernel packets, it is always a good idea to call MLError() before calling MLNextPacket().  MLError() will check the link for an error and return an error code.  You can find these codes listed in mathlink.h starting with MLEOK (indicating that no error has occurred).  The function MLErrorString() will convert the error code into a readable string that attempts to explain the error.  If you want your code to handle the error condition you must call MLClearError() before trying to use the link again.

MLENV env;
MLINK link;
mlapi_error err;

err = MLError(link);
if(err != MLEOK){
	printf("MathLink Error: %s\n", MLErrorString(env, err));
	MLClearError(link);
}

Answer

Here is a program that solves Exercise 3:

/*
 *  exercise3.c
 *
 *
 *  Created by Steve Wilson on 10/5/06.
 *  Copyright 2006 Wolfram Research Inc.. All rights reserved.
 *
 */

#include <stdio.h>
#include <string.h>
#include "mathlink.h"

typedef void * (*fname)(MLENV, MLINK);

void * getpacket(MLENV, MLINK);
void * getpacketcontents(MLENV, MLINK);
void * getinput(MLENV, MLINK);

void printerror(MLENV, MLINK);

int reporterrors = 0;

/*
This program makes use of fprintf and fflush to prevent buffered output.
The fflush is not strictly necessary but helps with debugging.

- Steve Wilson
*/

int main(int c, char **argv)
{
        MLENV env;
        MLINK link;
        long error;
        mlapi_result mlresult;
        fname nextfunction;

        env = MLInitialize((MLParametersPointer)0);
        if(env == (MLENV)0) goto E0;

        link = MLOpenArgcArgv(env, argv, argv + argc, &error);
        if(link == (MLINK)0) goto E1;

        mlresult = MLConnect(link);
        if(mlresult != MLSUCCESS){
                printerror(env,link);
                goto E2;
        }


        /* This program implements a small state machine to handle reading the
        packet types, reading the packet contents, and reading input from the user.*/
        
        nextfunction = getpacket;

        while(nextfunction != (fname)0){
                nextfunction = (fname)nextfunction(env, link);
        }

        MLClose(link);
        MLDeinitialize(env);

        return 0;

E2:     MLClose(link);
E1:     MLDeinitialize(env);
E0:     return 1;
}



void printerror(MLENV env, MLINK link)
{
        if(reporterrors){
                fprintf(stderr, "%s\n", MLErrorString(env, MLError(link)));
                fflush(stderr);
        }
}


void * getpacket(MLENV env, MLINK link)
{
        const char *str;
        mlapi_packet pkt;
        mlapi_error err;
        fname result;

        err = MLError(link);
        if(err != MLEOK){
                printerror(env, link);
                return (void *)0;
        }

        pkt = MLNextPacket(link);

        switch(pkt){
                case INPUTNAMEPKT:
                        MLGetString(link, &str);
                        fprintf(stdout, "%s", str);
                        fflush(stdout);
                        MLDisownString(link, str);
                        return (void *)getinput;
                case OUTPUTNAMEPKT:
                        MLGetString(link, &str);
                        fprintf(stdout, "%s", str);
                        fflush(stdout);
                        MLDisownString(link, str);
                        return (void *)getpacket;
                case RETURNPKT:
                case RETURNEXPRPKT:
                case RETURNTEXTPKT:
                case TEXTPKT:
                case DISPLAYPKT:
                case DISPLAYENDPKT:
                case SUSPENDPKT:
                        result = (fname)getpacketcontents(env, link);
                        fprintf(stdout,"\n\n");
                        fflush(stdout);
                        return (void *)result;
                case RESUMEPKT:
                        return (void *)getpacket;
                case MESSAGEPKT:
                case SYNTAXPKT:
                default:
                        MLNewPacket(link);
                        return (void *)getpacket;
        }

        return (void *)0;
}


void * getpacketcontents(MLENV env, MLINK link)
{
        const char *str;
        int integer;
        double real;
        long i, length;
        fname newfunction;
        mlapi_token tok;

        tok = MLGetNext(link);
        switch(tok){
                case MLTKSYM:
                        MLGetSymbol(link, &str);
                        fprintf(stdout, "%s", str);
                        fflush(stdout);
                        MLDisownSymbol(link, str);
                        break;
                case MLTKSTR:
                        MLGetString(link, &str);
                        fprintf(stdout, "\"%s\"", str);
                        fflush(stdout);
                        MLDisownString(link, str);
                        break;
                case MLTKINT:
                        MLGetInteger(link, &integer);
                        fprintf(stdout, "%d", integer);
                        fflush(stdout);
                        break;
                case MLTKREAL:
                        MLGetReal(link, &real);
                        fprintf(stdout, "%g", real);
                        fflush(stdout);
                        break;
                case MLTKFUNC:
                        MLGetArgCount(link, &length);
                        getpacketcontents(env, link);
                        fprintf(stdout, "[");
                        for(i = 0; i < length; i++){
                                newfunction = (fname)getpacketcontents(env, link);
                                if(newfunction == (fname)0) return (void *)0;
                                if(i != (length - 1)) fprintf(stdout, ", ");
                        }
                        fprintf(stdout, "]");
                        fflush(stdout);
                        break;
                case MLTKERROR:
                        printerror(env, link);
                default:
                        printf("Unrecognized MathLink token: %d\n", tok);
                        return (void *)0;
        }

        return getpacket;
}


void * getinput(MLENV env, MLINK link)
{
        char buffer[5000];

        memset((void *)buffer, 0, 5000 * sizeof(char));

        fgets(buffer, 5000, stdin);

        MLPutFunction(link, "EnterExpressionPacket", 1);
                        MLPutFunction(link, "ToExpression", 1);
                                MLPutString(link, buffer);
        MLEndPacket(link);

        MLFlush(link);

        fprintf(stdout, "\n");
        fflush(stdout);

        return getpacket;
}